/* -*-c++-*- IfcGears - www.ifcgears.com  - Copyright (C) 2011 Fabian Gerold
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 * OpenSceneGraph Public License for more details.
*/

//! @author Fabian Gerold
//! @date 2011-07-18
//! Credits: Original version in Java by Michael Theiler, www.openifctools.org

#define _USE_MATH_DEFINES 
#include <math.h>

#include "ifcgears/IFC2X4/include/IfcProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcCartesianPoint.h"
#include "ifcgears/IFC2X4/include/IfcParameterizedProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcArbitraryOpenProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcArbitraryClosedProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcCompositeProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcDerivedProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcCurve.h"
#include "ifcgears/IFC2X4/include/IfcArbitraryProfileDefWithVoids.h"
#include "ifcgears/IFC2X4/include/IfcBoundedCurve.h"
#include "ifcgears/IFC2X4/include/IfcCartesianTransformationOperator2D.h"
#include "ifcgears/IFC2X4/include/IfcRectangleProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcCircleProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcCircleHollowProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcTrapeziumProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcCenterLineProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcPositiveLengthMeasure.h"
#include "ifcgears/IFC2X4/include/IfcRectangleHollowProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcRoundedRectangleProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcPositiveLengthMeasure.h"
#include "ifcgears/IFC2X4/include/IfcLengthMeasure.h"
#include "ifcgears/IFC2X4/include/IfcNonNegativeLengthMeasure.h"
#include "ifcgears/IFC2X4/include/IfcEllipseProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcIShapeProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcAsymmetricIShapeProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcLShapeProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcUShapeProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcCShapeProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcZShapeProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcPlaneAngleMeasure.h"
#include "ifcgears/IFC2X4/include/IfcTShapeProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcAxis2Placement2D.h"
#include "ifcgears/IFC2X4/include/IfcNurbsProfile.h"
#include "ifcgears/IFC2X4/include/IfcRationalBSplineSurfaceWithKnots.h"

#include "ifcgears/model/IfcGearsModel.h"
#include "ifcgears/model/UnitConverter.h"
#include "ifcgears/model/IfcGearsException.h"

#include "carve/geom2d.hpp"
#include "carve/geom3d.hpp"
#include "carve/matrix.hpp"

#include "Utility.h"
#include "RepresentationConverter.h"
#include "PlacementConverter.h"
#include "ProfileConverter.h"

ProfileConverter::ProfileConverter( shared_ptr<UnitConverter> unit_converter )
	: m_unit_converter(unit_converter)
{
}

ProfileConverter::~ProfileConverter()
{
}

void ProfileConverter::setProfile( shared_ptr<IfcProfileDef> profile_def )
{
	// ENTITY IfcProfileDef SUPERTYPE OF(ONEOF(IfcArbitraryClosedProfileDef, IfcArbitraryOpenProfileDef, IfcCompositeProfileDef,
	//IfcDerivedProfileDef, IfcParameterizedProfileDef));
	shared_ptr<IfcArbitraryClosedProfileDef> arbitrary_closed = dynamic_pointer_cast<IfcArbitraryClosedProfileDef>(profile_def);
	if( arbitrary_closed )
	{
		convertIfcArbitraryClosedProfileDef( arbitrary_closed, m_paths );
		return;
	}

	shared_ptr<IfcArbitraryOpenProfileDef> arbitrary_open = dynamic_pointer_cast<IfcArbitraryOpenProfileDef>(profile_def);
	if( arbitrary_open )
	{
		convertIfcArbitraryOpenProfileDef( arbitrary_open, m_paths );
		return;
	}

	shared_ptr<IfcCompositeProfileDef> composite = dynamic_pointer_cast<IfcCompositeProfileDef>(profile_def);
	if( composite )
	{
		convertIfcCompositeProfileDef( composite, m_paths );
		return;
	}

	shared_ptr<IfcDerivedProfileDef> derived = dynamic_pointer_cast<IfcDerivedProfileDef>(profile_def);
	if( derived )
	{
		convertIfcDerivedProfileDef( derived, m_paths );
		return;
	}

	shared_ptr<IfcParameterizedProfileDef> parameterized = dynamic_pointer_cast<IfcParameterizedProfileDef>(profile_def);
	if( parameterized )
	{
		convertIfcParameterizedProfileDef( parameterized, m_paths );
		return;
	}

	shared_ptr<IfcNurbsProfile> nurbs = dynamic_pointer_cast<IfcNurbsProfile>(profile_def);
	if( nurbs )
	{
		convertIfcNurbsProfile( nurbs, m_paths );
		return;
	}
	
	std::stringstream sstr;
	sstr << "ProfileDef not supported: " << profile_def->classname();
	throw IfcGearsException( sstr.str() );
}

void ProfileConverter::addAndOptimize( const std::vector<carve::geom::vector<3> >& polygon, std::vector<std::vector<carve::geom::vector<3> > >& paths )
{
	if( polygon.size() < 1 )
	{
		return;
	}

	carve::geom3d::Vector point_previous = polygon.at(0);
	std::vector<carve::geom::vector<3> > polygon_add;
	polygon_add.push_back( point_previous );
	for( int i=1; i<polygon.size(); ++i )
	{
		carve::geom3d::Vector point = polygon.at(i);
		// omit duplicate points
		if( (point-point_previous).length() > 0.00001 )
		{
			polygon_add.push_back( point );
		}
	}
	paths.push_back(polygon_add);
}

void ProfileConverter::convertIfcArbitraryClosedProfileDef( const shared_ptr<IfcArbitraryClosedProfileDef>& profile,	std::vector<std::vector<carve::geom::vector<3> > >& paths )
{
	// subtype: IfcArbitraryProfileDefWithVoids

	//OuterCurve
	shared_ptr<IfcCurve> outer_curve = profile->m_OuterCurve;
	std::vector<carve::geom::vector<3> > curve_polygon;
	std::vector<carve::geom::vector<3> > segment_start_points;
	RepresentationConverter representation_converter( m_unit_converter );
	representation_converter.convertIfcCurve( outer_curve, curve_polygon, segment_start_points );

	deleteLastPoint( curve_polygon );
	addAndOptimize( curve_polygon, paths );

	//IfcArbitraryProfileDefWithVoids
	shared_ptr<IfcArbitraryProfileDefWithVoids> profile_with_voids = dynamic_pointer_cast<IfcArbitraryProfileDefWithVoids>(profile);
	if( profile_with_voids )
	{
		std::vector<shared_ptr<IfcCurve> > inner_curves = profile_with_voids->m_InnerCurves;
		for( int i = 0; i < inner_curves.size(); ++i ) 
		{
			shared_ptr<IfcCurve> inner_ifc_curve = inner_curves[i];
			std::vector<carve::geom::vector<3> > inner_curve_polygon;
			std::vector<carve::geom::vector<3> > segment_start_points;
			representation_converter.convertIfcCurve( inner_ifc_curve, inner_curve_polygon, segment_start_points );
			deleteLastPoint( inner_curve_polygon );
			addAndOptimize( inner_curve_polygon, paths );
		}
	}
}
	
void ProfileConverter::convertIfcArbitraryOpenProfileDef( const shared_ptr<IfcArbitraryOpenProfileDef>& profile,	std::vector<std::vector<carve::geom::vector<3> > >& paths )
{
	//subtype: IfcCenterLineProfileDef
	shared_ptr<IfcCurve> ifc_curve = profile->m_Curve;
	std::vector<carve::geom::vector<3> > polygon;
	std::vector<carve::geom::vector<3> > segment_start_points;

	RepresentationConverter representation_converter( m_unit_converter );
	representation_converter.convertIfcCurve( ifc_curve, polygon, segment_start_points );
	addAndOptimize( polygon, paths );

	//TODO IfcCenterLineProfileDef
	if( dynamic_pointer_cast<IfcCenterLineProfileDef>(profile))
	{
//			double t = unitConverter.getLengthInMeter(((IfcCenterLineProfileDef) profileDef).Thickness.value);
	}
}
	
void ProfileConverter::convertIfcCompositeProfileDef( const shared_ptr<IfcCompositeProfileDef>& composite_profile, std::vector<std::vector<carve::geom::vector<3> > >& paths )
{
	std::vector<int> temploop_counts;
	std::vector<int> tempcontour_counts;
	
	std::vector<shared_ptr<IfcProfileDef> >& profiles = composite_profile->m_Profiles;
	std::vector<shared_ptr<IfcProfileDef> >::iterator it;
	
	for( it=profiles.begin(); it!=profiles.end(); ++it )
	{
		shared_ptr<IfcProfileDef> profile_def = (*it);

		shared_ptr<IfcParameterizedProfileDef> parameterized = dynamic_pointer_cast<IfcParameterizedProfileDef>(profile_def);
		if( parameterized )
		{
			convertIfcParameterizedProfileDef( parameterized, paths );
			continue;
		}

		shared_ptr<IfcArbitraryOpenProfileDef> open = dynamic_pointer_cast<IfcArbitraryOpenProfileDef>(profile_def);
		if( open )
		{
			convertIfcArbitraryOpenProfileDef( open, paths );
			continue;
		}

		shared_ptr<IfcArbitraryClosedProfileDef> closed = dynamic_pointer_cast<IfcArbitraryClosedProfileDef>(profile_def);
		if( closed )
		{
			convertIfcArbitraryClosedProfileDef( closed, paths );
			continue;
		}

		shared_ptr<IfcCompositeProfileDef> composite = dynamic_pointer_cast<IfcCompositeProfileDef>(profile_def);
		if( composite )
		{
			convertIfcCompositeProfileDef( composite, paths );
			continue;
		}

		shared_ptr<IfcDerivedProfileDef> derived = dynamic_pointer_cast<IfcDerivedProfileDef>(profile_def);
		if( derived )
		{
			convertIfcDerivedProfileDef( derived, paths );
			continue;
		}

		std::stringstream sstr;
		sstr << "ProfileDef not supported: " << profile_def->classname();
		throw IfcGearsException( sstr.str() );
	}
}
	
void ProfileConverter::convertIfcDerivedProfileDef( const shared_ptr<IfcDerivedProfileDef>& derived_profile, std::vector<std::vector<carve::geom::vector<3> > >& paths )
{
	ProfileConverter temp_profiler( m_unit_converter );
	temp_profiler.setProfile( derived_profile->m_ParentProfile );
	const std::vector<std::vector<carve::geom::vector<3> > >& parent_paths = temp_profiler.getCoordinates();

	shared_ptr<IfcCartesianTransformationOperator2D> transf_op_2D = derived_profile->m_Operator;
	
	double length_factor = m_unit_converter->getLengthInMeterFactor();
	carve::math::Matrix transform( carve::math::Matrix::IDENT() );
	PlacementConverter::convertTransformationOperator( transf_op_2D, transform, length_factor );
	for( int i=0; i<parent_paths.size(); ++i ) 
	{
		const std::vector<carve::geom::vector<3> >& loop_parent = parent_paths[i];
		std::vector<carve::geom::vector<3> > loop;

		for( int j=0; j<parent_paths.size(); ++j ) 
		{
			const carve::geom3d::Vector& pt = loop_parent.at(j);
			loop.push_back( transform*pt );
		}
		paths.push_back(loop);
	}
}
	
void ProfileConverter::convertIfcParameterizedProfileDef( const shared_ptr<IfcParameterizedProfileDef>& profile, std::vector<std::vector<carve::geom::vector<3> > >& target_paths )
{
	//IfcParameterizedProfileDef ABSTRACT SUPERTYPE OF (ONEOF
	//	(IfcCShapeProfileDef, IfcCircleProfileDef, IfcEllipseProfileDef, IfcIShapeProfileDef, IfcLShapeProfileDef,
	//	IfcRectangleProfileDef, IfcTShapeProfileDef, IfcTrapeziumProfileDef, IfcUShapeProfileDef, IfcZShapeProfileDef))
	
	double length_factor = m_unit_converter->getLengthInMeterFactor();
	double angle_factor = m_unit_converter->getAngleInRadianFactor();
	std::vector<carve::geom::vector<3> > outer_loop;
	std::vector<std::vector<carve::geom::vector<3> > > paths;

	if( dynamic_pointer_cast<IfcRectangleProfileDef>(profile) )
	{
		//subtypes: IfcRectangleHollowProfileDef, IfcRoundedRectangleProfileDef
			
		shared_ptr<IfcRectangleProfileDef> rectangle = dynamic_pointer_cast<IfcRectangleProfileDef>(profile);
		if( rectangle->m_XDim && rectangle->m_YDim )
		{
			double x = rectangle->m_XDim->m_value*length_factor;
			double y = rectangle->m_YDim->m_value*length_factor;
					
			//RectangleHollow
			if(dynamic_pointer_cast<IfcRectangleHollowProfileDef>(profile))
			{
				shared_ptr<IfcRectangleHollowProfileDef> hollow = dynamic_pointer_cast<IfcRectangleHollowProfileDef>(profile);
				
				if( hollow->m_WallThickness )
				{
					//Parameter:
					double t = hollow->m_WallThickness->m_value*length_factor;
					double r1 = 0;
					if(hollow->m_OuterFilletRadius)
					{
						r1 = hollow->m_InnerFilletRadius->m_value*length_factor;
					}

					double r2 = 0;
					if(hollow->m_InnerFilletRadius)
					{
						r2 = hollow->m_InnerFilletRadius->m_value*length_factor;	
					}
					
					//Outer
					if(r1 != 0)
					{
						addArc( outer_loop, r1, 0, 		M_PI_2,  x*0.5-r1,  y*0.5-r1);
						addArc( outer_loop, r1, M_PI_2, 	M_PI_2, -x*0.5+r1,  y*0.5-r1);
						addArc( outer_loop, r1, M_PI,		M_PI_2, -x*0.5+r1, -y*0.5+r1);
						addArc( outer_loop, r1, 3*M_PI_2,	M_PI_2,  x*0.5-r1, -y*0.5+r1);
					}
					else
					{
						 outer_loop.push_back( carve::geom::VECTOR(-x*0.5,-y*0.5, 0) );
						 outer_loop.push_back( carve::geom::VECTOR( x*0.5,-y*0.5, 0) );
						 outer_loop.push_back( carve::geom::VECTOR( x*0.5, y*0.5, 0) );
						 outer_loop.push_back( carve::geom::VECTOR(-x*0.5, y*0.5, 0) );
					}

					//Inner
					std::vector<carve::geom::vector<3> > inner_loop;
					x -= 2*t;
					y -= 2*t;
					if(r2 != 0)
					{
						addArc( inner_loop, r2, 0, 		M_PI_2,  x*0.5-r2,  y*0.5-r2);
						addArc( inner_loop, r2, M_PI_2, 	M_PI_2, -x*0.5+r2,  y*0.5-r2);
						addArc( inner_loop, r2, M_PI, 	M_PI_2, -x*0.5+r2, -y*0.5+r2);
						addArc( inner_loop, r2, 3*M_PI_2,	M_PI_2,  x*0.5-r2, -y*0.5+r2);
					}
					else
					{
						inner_loop.push_back( carve::geom::VECTOR(-x*0.5,-y*0.5, 0));
						inner_loop.push_back( carve::geom::VECTOR( x*0.5,-y*0.5, 0));
						inner_loop.push_back( carve::geom::VECTOR( x*0.5, y*0.5, 0));
						inner_loop.push_back( carve::geom::VECTOR(-x*0.5, y*0.5, 0));
					}
					paths.push_back(outer_loop);
					paths.push_back(inner_loop);
				}
			}
		
			//RoundedRectangle
			else if(dynamic_pointer_cast<IfcRoundedRectangleProfileDef>(profile))
			{
				shared_ptr<IfcRoundedRectangleProfileDef> rounded_rectangle = dynamic_pointer_cast<IfcRoundedRectangleProfileDef>(profile);
				if( rounded_rectangle->m_RoundingRadius )
				{
					double rr = rounded_rectangle->m_RoundingRadius->m_value*length_factor;
					addArc( outer_loop, rr, 0, 		M_PI_2,  x*0.5-rr,  y*0.5-rr);
					addArc( outer_loop, rr, M_PI_2, 	M_PI_2, -x*0.5+rr,  y*0.5-rr);
					addArc( outer_loop, rr, M_PI, 	M_PI_2, -x*0.5+rr, -y*0.5+rr);
					addArc( outer_loop, rr, 3*M_PI_2,	M_PI_2,  x*0.5-rr, -y*0.5+rr);
					paths.push_back(outer_loop);
				}
			}
			else
			{
				// it's a standard rectangle
				outer_loop.push_back( carve::geom::VECTOR(-x*0.5,-y*0.5, 0));
				outer_loop.push_back( carve::geom::VECTOR( x*0.5,-y*0.5, 0));
				outer_loop.push_back( carve::geom::VECTOR( x*0.5, y*0.5, 0));
				outer_loop.push_back( carve::geom::VECTOR(-x*0.5, y*0.5, 0));
				paths.push_back(outer_loop);
			}
		}
	}
		
	//Trapezium
	else if( dynamic_pointer_cast<IfcTrapeziumProfileDef>(profile) )
	{
		shared_ptr<IfcTrapeziumProfileDef> trapezium = dynamic_pointer_cast<IfcTrapeziumProfileDef>(profile);
		if( trapezium->m_BottomXDim && trapezium->m_TopXDim && trapezium->m_TopXOffset && trapezium->m_YDim )
		{
			double xBottom = trapezium->m_BottomXDim->m_value*length_factor;
			double xTop = trapezium->m_TopXDim->m_value*length_factor;
			double xOffset = trapezium->m_TopXOffset->m_value*length_factor;
			double y = trapezium->m_YDim->m_value*length_factor;
			outer_loop.push_back( carve::geom::VECTOR(-xBottom*0.5,				-y*0.5,		0.0));
			outer_loop.push_back( carve::geom::VECTOR( xBottom*0.5,				-y*0.5,		0.0));
			outer_loop.push_back( carve::geom::VECTOR(-xBottom*0.5+xOffset+xTop,	y*0.5,		0.0));	
			outer_loop.push_back( carve::geom::VECTOR(-xBottom*0.5+xOffset,		y*0.5,		0.0));
			paths.push_back(outer_loop);
		}
	}
		
	//Circle
	else if( dynamic_pointer_cast<IfcCircleProfileDef>(profile) )
	{
		//subtype: IfcCircleHollowProfileDef
			
		shared_ptr<IfcCircleProfileDef> circleProfileDef = dynamic_pointer_cast<IfcCircleProfileDef>(profile);
		double radius = circleProfileDef->m_Radius->m_value*length_factor;
			
		int segments = (int) (radius*2*M_PI*10);
		if (segments < 16) segments = 16;
		if (segments > 100) segments = 100;
			
		double angle = 0;
		for(int i=0; i<segments; ++i)
		{
			outer_loop.push_back( carve::geom::VECTOR( (radius * cos(angle)),	 (radius * sin(angle)), 0.f));
			angle += 2*M_PI / segments;
		}
		paths.push_back(outer_loop);
			
		//CircleHollow
		std::vector<carve::geom::vector<3> > inner_loop;
		if( dynamic_pointer_cast<IfcCircleHollowProfileDef>(profile) )
		{
			shared_ptr<IfcCircleHollowProfileDef> hollow = dynamic_pointer_cast<IfcCircleHollowProfileDef>(profile);
			angle = 0;
			radius -= hollow->m_WallThickness->m_value*length_factor;
			int segments2 = (int) (radius*2*M_PI*10);
			if (segments2 < 16) segments2 = 16;
			if (segments2 > 100) segments2 = 100;
			for(int i=0; i<segments2; ++i)
			{
				inner_loop.push_back( carve::geom::VECTOR( (radius * cos(angle)),  (radius * sin(angle)), 0.f));
				angle += 2*M_PI / segments2;
			}
			paths.push_back(inner_loop);
		}
	}
		
	//Ellipse
	else if(dynamic_pointer_cast<IfcEllipseProfileDef>(profile))
	{
		shared_ptr<IfcEllipseProfileDef> ellipseProfileDef = dynamic_pointer_cast<IfcEllipseProfileDef>(profile);
		if( ellipseProfileDef->m_SemiAxis1 )
		{
			if( ellipseProfileDef->m_SemiAxis2 )
			{
				double xRadius = ellipseProfileDef->m_SemiAxis1->m_value*length_factor;
				double yRadius = ellipseProfileDef->m_SemiAxis2->m_value*length_factor;

				double radiusMax = std::max(xRadius, yRadius);
				int segments = (int) (radiusMax*2*M_PI*10);
				if (segments < 16) segments = 16;
				if (segments > 100) segments = 100;

				double angle=0;
				for(int i = 0; i < segments; ++i) 
				{
					outer_loop.push_back( carve::geom::VECTOR( (xRadius * cos(angle)),  (yRadius * sin(angle)), 0.f));
					angle += 2*M_PI / segments;
				}
				paths.push_back(outer_loop);
			}
		}
	}
		
	//I-Shape-Profile
	else if(dynamic_pointer_cast<IfcIShapeProfileDef>(profile))
	{
		//subtype: IfcAsymmetricIShapeProfileDef
			
		shared_ptr<IfcIShapeProfileDef> i_shape = dynamic_pointer_cast<IfcIShapeProfileDef>(profile);
		if( i_shape->m_OverallDepth && i_shape->m_OverallWidth && i_shape->m_WebThickness && i_shape->m_FlangeThickness )
		{
			//Parameter:
			double h = i_shape->m_OverallDepth->m_value*length_factor;
			double b = i_shape->m_OverallWidth->m_value*length_factor;
			double tw = i_shape->m_WebThickness->m_value*length_factor;
			double tf = i_shape->m_FlangeThickness->m_value*length_factor;
			double r = 0;
			if(i_shape->m_FilletRadius)
			{
				r = i_shape->m_FilletRadius->m_value*length_factor;
			}

			//RIGHT BOTTOM FLANGE
			outer_loop.push_back( carve::geom::VECTOR( b*0.5,  -h*0.5, 0));
			outer_loop.push_back( carve::geom::VECTOR( b*0.5,  (-h*0.5+tf), 0));
			//RIGHT BOTTOM FILLET
			if(r != 0)
			{
				addArc( outer_loop, r, 3*M_PI_2, -M_PI_2, tw*0.5+r, -h*0.5+tf+r);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( tw*0.5,  (-h*0.5+tf), 0.f) );
			}
			
			//check if profile is asymmetric
			shared_ptr<IfcAsymmetricIShapeProfileDef> asymIProfile = dynamic_pointer_cast<IfcAsymmetricIShapeProfileDef>(profile);
			if( asymIProfile )
			{
				if( asymIProfile->m_TopFlangeWidth )
				{
					//Parameter:
					double bTop = asymIProfile->m_TopFlangeWidth->m_value*length_factor;
					double tfTop = tf;

					if(asymIProfile->m_TopFlangeThickness )
					{
						tfTop = asymIProfile->m_TopFlangeThickness->m_value*length_factor;
					}
					double rTop = r;
					if(asymIProfile->m_TopFlangeFilletRadius)
					{
						rTop = asymIProfile->m_TopFlangeFilletRadius->m_value*length_factor;
					}

					//RIGHT TOP FILLET
					if( rTop != 0 )
					{
						addArc( outer_loop, rTop, M_PI, -M_PI_2, tw*0.5+rTop, h*0.5-tfTop-rTop);
					}
					else
					{
						outer_loop.push_back( carve::geom::VECTOR( tw*0.5,  (h*0.5-tfTop), 0.f));
					}
					//RIGHT TOP FLANGE
					outer_loop.push_back( carve::geom::VECTOR( bTop*0.5,  (h*0.5-tfTop), 0));
					outer_loop.push_back( carve::geom::VECTOR( bTop*0.5,  h*0.5, 0));
				}
			}
			else
			{
				//symmetric: mirror horizontal along x-Axis
				int loop_count = outer_loop.size();
				for( int i = loop_count-1; i >= 0; --i )
				{
					carve::geom3d::Vector& p = outer_loop.at(i);
					outer_loop.push_back( carve::geom::VECTOR( p.x, -p.y, 0) );
				}
			}

			//MIRROR VERTICAL ALONG Y-AXIS
			int loop_count = outer_loop.size();
			for( int i = loop_count-1; i >= 0; --i )
			{
				carve::geom3d::Vector& p = outer_loop.at(i);
				outer_loop.push_back( carve::geom::VECTOR( -p.x, p.y, 0) );
			}
			paths.push_back(outer_loop);
		}
	}
		
	//L-Shape-Profile
	else if(dynamic_pointer_cast<IfcLShapeProfileDef>(profile))
	{
		shared_ptr<IfcLShapeProfileDef> l_shape = dynamic_pointer_cast<IfcLShapeProfileDef>(profile);

		if( l_shape->m_Depth && l_shape->m_Thickness )
		{
			//Parameter:
			double h = l_shape->m_Depth->m_value*length_factor;
			double b = h;
		
			if(l_shape->m_Width)
			{
				b = l_shape->m_Width->m_value*length_factor;
			}
		
			double t = l_shape->m_Thickness->m_value*length_factor;
		
			double r1 = 0;
			if(l_shape->m_FilletRadius)
			{
				r1 = l_shape->m_FilletRadius->m_value*length_factor;
			}
		
			double r2 = 0;
			if(l_shape->m_EdgeRadius)
			{
				r2 = l_shape->m_EdgeRadius->m_value*length_factor;
			}
		
			double ls = 0;
			if(l_shape->m_LegSlope)
			{
				ls = l_shape->m_LegSlope->m_value*angle_factor;
			}
			
			//STARTING AT LEFT BOTTOM CORNER (counter-clockwise)
			outer_loop.push_back( carve::geom::VECTOR( -b*0.5,  -h*0.5, 0));
			outer_loop.push_back( carve::geom::VECTOR( b*0.5,  -h*0.5, 0));
			//FIRST EDGE
			if(r2 != 0)
			{
				addArc( outer_loop, r2, 0, M_PI_2-ls, b*0.5-r2, -h*0.5+t-r2);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( b*0.5,  (-h*0.5+t), 0));
			}

			//FILLET
			//Help Values for Fillet Centre:
			double s = sin(ls);
			double c = cos(ls);
			double z1 = (-s*((c-s)*(r1+r2+t)-c*b+s*h))/(2*c*c-1);
			double z2 = (-s*((c-s)*(r1+r2+t)-c*h+s*b))/(2*c*c-1);
			if(r1 != 0)
			{
				addArc( outer_loop, r1, 3*M_PI_2-ls, -M_PI_2+2*ls, -b*0.5+t+z2+r1, -h*0.5+t+z1+r1);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( (-b*0.5+t+z2),  (-h*0.5+t+z1), 0));
			}
			//SECOND EDGE
			if(r2 != 0)
			{
				addArc( outer_loop, r2, ls, M_PI_2-ls, -b*0.5+t-r2, h*0.5-r2);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( (-b*0.5+t),  h*0.5, 0));
			}
			//ENDING AT LEFT UPPER CORNER
			outer_loop.push_back( carve::geom::VECTOR( -b*0.5,  h*0.5, 0));

			paths.push_back( outer_loop );
		}
	}

	//U-Shape-Profile
	else if(dynamic_pointer_cast<IfcUShapeProfileDef>(profile))
	{
		shared_ptr<IfcUShapeProfileDef> u_shape = dynamic_pointer_cast<IfcUShapeProfileDef>(profile);

		if( u_shape->m_Depth && u_shape->m_FlangeWidth && u_shape->m_WebThickness && u_shape->m_FlangeThickness )
		{
			//Parameter:
			double h = u_shape->m_Depth->m_value*length_factor;
			double b = u_shape->m_FlangeWidth->m_value*length_factor;
			double tw = u_shape->m_WebThickness->m_value*length_factor;
			double tf = u_shape->m_FlangeThickness->m_value*length_factor;
			double r1 = 0;
			if(u_shape->m_FilletRadius)
			{
				r1 = u_shape->m_FilletRadius->m_value*length_factor;
			}
			double r2 = 0;
			if(u_shape->m_EdgeRadius)
			{
				r2 = u_shape->m_EdgeRadius->m_value*length_factor;
			}
			double fs = 0;
			if(u_shape->m_FlangeSlope)
			{
				fs = u_shape->m_FlangeSlope->m_value*angle_factor;
			}
			
			//STARTING AT LEFT BOTTOM CORNER (counter-clockwise)
			outer_loop.push_back( carve::geom::VECTOR( -b*0.5,  -h*0.5, 0));
			outer_loop.push_back( carve::geom::VECTOR( b*0.5,  -h*0.5, 0));

			//FIRST EDGE
			double z = tan(fs)*(b*0.5-r2);
			if( r2 != 0 )
			{
				addArc( outer_loop, r2, 0, M_PI_2-fs, b*0.5-r2, -h*0.5+tf-z-r2);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( b*0.5,  (-h*0.5+tf-z), 0));
			}

			//FILLET
			z = tan(fs)*(b*0.5-tw-r1);
			if( r1 != 0 )
			{
				addArc( outer_loop, r1, 3*M_PI_2-fs, -M_PI_2+fs, -b*0.5+tw+r1, -h*0.5+tf+z+r1);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( (-b*0.5+tw),  (-h*0.5+tf+z), 0));
			}

			//mirror horizontal along x-Axis
			int loop_count = outer_loop.size();
			for( int i = loop_count-1; i >= 0; --i )
			{
				carve::geom3d::Vector& p = outer_loop.at(i);
				outer_loop.push_back( carve::geom::VECTOR( p.x, -p.y, 0) );
			}
		}
	}
		
	//C-Shape-Profile
	else if(dynamic_pointer_cast<IfcCShapeProfileDef>(profile))
	{
		shared_ptr<IfcCShapeProfileDef> c_shape = dynamic_pointer_cast<IfcCShapeProfileDef>(profile);
		if( c_shape->m_Depth && c_shape->m_Width && c_shape->m_Girth && c_shape->m_WallThickness )
		{
			//Parameter:
			double h = c_shape->m_Depth->m_value*length_factor;
			double b = c_shape->m_Width->m_value*length_factor;
			double g = c_shape->m_Girth->m_value*length_factor;
			double t = c_shape->m_WallThickness->m_value*length_factor;
			double r1 = 0;
			if(c_shape->m_InternalFilletRadius)
			{
				r1 = c_shape->m_InternalFilletRadius->m_value*length_factor;
			}
			
			//STARTING AT OUTER LEFT BOTTOM CORNER (counter-clockwise)
			if(r1 != 0)
			{
				addArc( outer_loop, r1+t, M_PI, M_PI_2, -b*0.5+t+r1, -h*0.5+t+r1);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( -b*0.5,  -h*0.5, 0));
			}
			//OUTER RIGHT BOTTOM CORNER
			if(r1 != 0)
			{
				addArc( outer_loop, r1+t, 3*M_PI_2, M_PI_2, b*0.5-t-r1, -h*0.5+t+r1);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( b*0.5,  -h*0.5, 0));
			}
			//BOTTOM GIRTH
			outer_loop.push_back( carve::geom::VECTOR( b*0.5,  (-h*0.5+g), 0));
			outer_loop.push_back( carve::geom::VECTOR( (b*0.5-t),  (-h*0.5+g), 0));
			//INNER RIGHT BOTTOM CORNER
			if(r1 != 0)
			{
				addArc( outer_loop, r1, 0, -M_PI_2, b*0.5-t-r1, -h*0.5+t+r1);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( (b*0.5-t),  (-h*0.5+t), 0));
			}
			//INNER LEFT BOTTOM CORNER
			if(r1 != 0)
			{
				addArc( outer_loop, r1, 3*M_PI_2, -M_PI_2, -b*0.5+t+r1, -h*0.5+t+r1);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( (-b*0.5+t),  (-h*0.5+t), 0));
			}
			//mirror horizontal along x-Axis
			int loop_count = outer_loop.size();
			for( int i = loop_count-1; i >= 0; --i )
			{
				carve::geom3d::Vector& p = outer_loop.at(i);
				outer_loop.push_back( carve::geom::VECTOR( p.x, -p.y, 0) );
			}
			paths.push_back(outer_loop);
		}
	}
		
	//Z-Shape-Profile
	else if(dynamic_pointer_cast<IfcZShapeProfileDef>(profile))
	{
		shared_ptr<IfcZShapeProfileDef> z_shape = dynamic_pointer_cast<IfcZShapeProfileDef>(profile);
		if( z_shape->m_Depth && z_shape->m_FlangeWidth && z_shape->m_WebThickness && z_shape->m_FlangeThickness )
		{
			//Parameter:
			double h = z_shape->m_Depth->m_value*length_factor;
			double b = z_shape->m_FlangeWidth->m_value*length_factor;
			double tw = z_shape->m_WebThickness->m_value*length_factor;
			double tf = z_shape->m_FlangeThickness->m_value*length_factor;
			double r1 = 0;
			if(z_shape->m_FilletRadius)
			{
				r1 = z_shape->m_FilletRadius->m_value*length_factor;
			}

			double r2 = 0;
			if(z_shape->m_EdgeRadius)
			{
				r2 = z_shape->m_EdgeRadius->m_value*length_factor;
			}

			//STARTING WITH LEFT UPPER CORNER (counter-clockwise)
			outer_loop.push_back( carve::geom::VECTOR( (-tw*0.5),  -h*0.5, 0));
			outer_loop.push_back( carve::geom::VECTOR( (b-tw*0.5),  -h*0.5, 0));
			//FIRST EDGE
			if(r2 != 0)
			{
				addArc( outer_loop, r2, 0, M_PI_2, b-tw*0.5-r2, -h*0.5+tf-r2);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( (b-tw*0.5),  (-h*0.5+tf), 0));
			}

			//FILLET
			if(r1 != 0)
			{
				addArc( outer_loop, r1, 3*M_PI_2, -M_PI_2, tw*0.5+r1, -h*0.5+tf+r1);
			}
			else
			{
				outer_loop.push_back( carve::geom::VECTOR( (tw*0.5),  (-h*0.5+tf), 0));
			}

			//mirror horizontal and vertical
			int loop_count = outer_loop.size();
			for(int i = 0; i < loop_count; ++i)
			{
				carve::geom3d::Vector& p = outer_loop.at(i);
				outer_loop.push_back( carve::geom::VECTOR( -p.x, -p.y, 0) );
			}
			paths.push_back(outer_loop);
		}
	}
		
	//T-Shape-Profile
	else if(dynamic_pointer_cast<IfcTShapeProfileDef>(profile))
	{
		shared_ptr<IfcTShapeProfileDef> tShapeProfileDef = dynamic_pointer_cast<IfcTShapeProfileDef>(profile);
		//Parameter:
		double h = tShapeProfileDef->m_Depth->m_value*length_factor;
		double b = tShapeProfileDef->m_FlangeWidth->m_value*length_factor;
		double tw = tShapeProfileDef->m_WebThickness->m_value*length_factor*0.5;
		double tf = tShapeProfileDef->m_FlangeThickness->m_value*length_factor;
		
		double r1 = 0;
		if(tShapeProfileDef->m_FilletRadius)
		{
			r1 = tShapeProfileDef->m_FilletRadius->m_value*length_factor;
		}

		double r2 = 0;
		if(tShapeProfileDef->m_FlangeEdgeRadius)
		{
			r2 = tShapeProfileDef->m_FlangeEdgeRadius->m_value*length_factor;
		}
		
		double r3 = 0;
		if(tShapeProfileDef->m_WebEdgeRadius)
		{
			r3 = tShapeProfileDef->m_WebEdgeRadius->m_value*length_factor;
		}
		double fs = 0;
		
		if(tShapeProfileDef->m_FlangeSlope)
		{
			fs = tShapeProfileDef->m_FlangeSlope->m_value*angle_factor;
		}

		double ws = 0;
		if(tShapeProfileDef->m_WebSlope)
		{
			ws = tShapeProfileDef->m_WebSlope->m_value*angle_factor;
		}
				
		//STARTING WITH LEFT UPPER CORNER (counter-clockwise)
		outer_loop.push_back( carve::geom::VECTOR( -b*0.5,  h*0.5, 0));
		//FIRST FLANGE EDGE
		double zf = tan(fs)*(b/4-r2);
		double zw = tan(ws)*(h*0.5-r3);
		if(r2 != 0)
		{
			addArc( outer_loop, r2, M_PI, M_PI_2-fs, -b*0.5+r2, h*0.5-tf+zf+r2);
		}
		else
		{
			outer_loop.push_back( carve::geom::VECTOR( -b*0.5,  (h*0.5-tf+zf), 0));
		}
		//FILLET
		double cf = cos(fs);
		double sf = sin(fs);
		double cw = cos(ws);
		double sw = sin(ws);
		double z1 = (sf*((b-2*(r1+r2+tw-zw))*cw-2*(h-r3-r1-tf+zf)*sw)) / (2*(cf*cw-sf*sw));
		double z2 = tan(ws)*(h-r3-r1-z1-tf+zf);
		if(r1 != 0)
		{
			addArc( outer_loop, r1, M_PI_2-fs, -M_PI_2+fs+ws, -tw+zw-z2-r1, h*0.5-tf+zf-z1-r1);
		}
		else
		{
			outer_loop.push_back( carve::geom::VECTOR( (-tw+zw-z2),  (h*0.5-tf+zf-z1), 0));
		}

		//FIRST WEB EDGE
		if(r3 != 0)
		{
			addArc( outer_loop, r3, M_PI+ws, M_PI_2-ws, -tw+zw+r3, -h*0.5+r3);
		}
		else
		{
			outer_loop.push_back( carve::geom::VECTOR( (-tw+zw), -h*0.5, 0));
		}
		
		//mirror vertical along y-Axis
		int loop_count = outer_loop.size();
		for( int i = loop_count-1; i >= 0; --i )
		{
			carve::geom3d::Vector& p = outer_loop.at(i);
			outer_loop.push_back( carve::geom::VECTOR( p.x, -p.y, 0) );
		}
		paths.push_back(outer_loop);
	}
		
	//not supported ProfileDef
	else
	{
		std::stringstream strs;
		strs << "IfcProfileDef not supported: " << profile->classname();
		throw IfcGearsException( strs.str() );
	}
		
	//transforming the profile
	if( profile->m_Position )
	{
		shared_ptr<IfcAxis2Placement2D> axis2Placement2D = profile->m_Position;

		carve::math::Matrix transform( carve::math::Matrix::IDENT() );
		PlacementConverter::convertIfcPlacement( axis2Placement2D, transform, length_factor );

		for(int i = 0; i < paths.size(); ++i )
		{
			std::vector<carve::geom3d::Vector>& path_loop = paths[i];
			for(int j = 0; j < path_loop.size(); ++j )
			{
				carve::geom3d::Vector& pt = path_loop.at(j);
				pt = transform*pt;
			}
			target_paths.push_back( path_loop );
		}
	}
}

void ProfileConverter::convertIfcNurbsProfile( const shared_ptr<IfcNurbsProfile>& nurbs_profile, std::vector<std::vector<carve::geom::vector<3> > >& paths )
{
	std::stringstream err;
	shared_ptr<IfcRationalBSplineSurfaceWithKnots> surface = dynamic_pointer_cast<IfcRationalBSplineSurfaceWithKnots>(nurbs_profile->m_Surface);
	if( !surface )
	{
		return;
	}
	std::vector<carve::geom::vector<3> > loop;
	double length_factor = m_unit_converter->getLengthInMeterFactor();
	std::vector<std::vector<shared_ptr<IfcCartesianPoint> > >& vec_control_points = surface->m_ControlPointsList;
	std::vector<std::vector<double> >& vec_weights = surface->m_WeightsData;

	// o------------<------------o
	// |     --> xi              |
	// |    |                    |
	// v    v                    ^
	// |    eta                  |
	// |                         |
	// o------------>------------o
	// insert control points at xi=0/1 and eta=0/1
	
	RepresentationConverter converter( m_unit_converter );

	// xi = 0
	std::vector<shared_ptr<IfcCartesianPoint> >& vec_control_points_eta0 = vec_control_points[0];
	std::vector<shared_ptr<IfcCartesianPoint> >::iterator it_xi0 = vec_control_points_eta0.begin();
	for( ; it_xi0 != vec_control_points_eta0.end(); ++it_xi0 )
	{
		shared_ptr<IfcCartesianPoint>& ifc_point = (*it_xi0);
		carve::geom3d::Vector point;
		converter.convertIfcCartesianPoint( ifc_point, point );
		loop.push_back(point);
	}

	// eta = 1
	std::vector<std::vector<shared_ptr<IfcCartesianPoint> > >::iterator it_eta1 = vec_control_points.begin();
	std::vector<std::vector<shared_ptr<IfcCartesianPoint> > >::iterator it_eta1_last = vec_control_points.end();
	++it_eta1;
	--it_eta1_last;
	for( ; it_eta1 != it_eta1_last; ++it_eta1 )
	{
		std::vector<shared_ptr<IfcCartesianPoint> >& vec_eta = *it_eta1;
		shared_ptr<IfcCartesianPoint>& ifc_point = vec_eta.back();
		carve::geom3d::Vector point;
		converter.convertIfcCartesianPoint( ifc_point, point );
		loop.push_back(point);
	}

	// xi = 1
	std::vector<shared_ptr<IfcCartesianPoint> >& vec_control_points_eta1 = vec_control_points[vec_control_points.size()-1];
	std::vector<shared_ptr<IfcCartesianPoint> >::reverse_iterator it_control_points_reverse = vec_control_points_eta1.rbegin();
	for( ; it_control_points_reverse != vec_control_points_eta1.rend(); ++it_control_points_reverse )
	{
		shared_ptr<IfcCartesianPoint>& ifc_point = (*it_control_points_reverse);
		carve::geom3d::Vector point;
		converter.convertIfcCartesianPoint( ifc_point, point );
		loop.push_back(point);
	}

	// eta = 0
	std::vector<std::vector<shared_ptr<IfcCartesianPoint> > >::reverse_iterator it_eta0 = vec_control_points.rbegin();
	std::vector<std::vector<shared_ptr<IfcCartesianPoint> > >::reverse_iterator it_eta0_last = vec_control_points.rend();
	++it_eta0;
	--it_eta0_last;
	for( ; it_eta0 != it_eta0_last; ++it_eta0 )
	{
		std::vector<shared_ptr<IfcCartesianPoint> >& vec_eta = *it_eta1;
		shared_ptr<IfcCartesianPoint>& ifc_point = vec_eta.back();
		carve::geom3d::Vector point;
		converter.convertIfcCartesianPoint( ifc_point, point );
		loop.push_back(point);
	}
	paths.push_back(loop);
}
	
void ProfileConverter::deleteLastPoint( std::vector<carve::geom::vector<3> >& coords )
{
	while(true)
	{
		if( coords.size() < 1 )
		{
			return;
		}
		carve::geom::vector<3>& first = coords.at(0);
		carve::geom::vector<3>& last = coords.at(coords.size()-1);

		if( (last-first).length() < 0.0001 )
		{
			coords.erase(coords.end()-1);
		}
		else
		{
			return;
		}
	}
}

void ProfileConverter::addArc( std::vector<carve::geom::vector<3> >& coords, double radius, double startAngle, double openingAngle, double xM, double yM, int segments )
{
	double angle = startAngle;
	double angle_delta = openingAngle / (double)segments;
	for( int i=0; i<segments; ++i )
	{	
		coords.push_back( carve::geom::VECTOR( (radius * cos(angle)+xM),  (radius * sin(angle)+yM), 0) );
		angle += angle_delta;
	}
}

void ProfileConverter::addFullArc( std::vector<carve::geom::vector<3> >& coords, double radius, double startAngle, double openingAngle, double xM, double yM, int segments )
{
	double angle = startAngle;
	double angle_delta = openingAngle / (double)(segments-1);
	for( int i=0; i<segments; ++i )
	{	
		coords.push_back( carve::geom::VECTOR( radius * cos(angle) + xM,  radius * sin(angle) + yM, 0 ) );
		angle += angle_delta;
	}
}
